Skip to content

fix: reject impossible CALYPSO atom choices - #382

Open
njzjz-bot wants to merge 1 commit into
deepmodeling:masterfrom
njzjz-bot:fix/issue-356-calypso-atom-choices
Open

fix: reject impossible CALYPSO atom choices#382
njzjz-bot wants to merge 1 commit into
deepmodeling:masterfrom
njzjz-bot:fix/issue-356-calypso-atom-choices

Conversation

@njzjz-bot

Copy link
Copy Markdown

Summary

  • compare nested atom-choice differences against an empty set correctly
  • raise ValueError before entering the random selection loop
  • add a regression test for [["Li"], ["Li"]]

Tests

  • PYTHONPATH=tests python -m unittest -v tests.exploration.test_make_task_group_from_config.TestMakeCalyTaskGroupFromConfig.test_rejects_impossible_random_atom_choices tests.exploration.test_make_task_group_from_config.TestMakeCalyTaskGroupFromConfig.test_caly_task_group
  • ruff format --check dpgen2/exploration/task/caly_task_group.py tests/exploration/test_make_task_group_from_config.py
  • isort --check-only dpgen2/exploration/task/caly_task_group.py tests/exploration/test_make_task_group_from_config.py
  • git diff --check

Closes #356

Coding agent: Codex
Codex version: codex-cli 0.149.0
Model: gpt-5.6-sol
Reasoning effort: xhigh

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working labels Aug 23, 2026
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 312172fa-88f6-495e-b98f-c1dadf1af8c7

📥 Commits

Reviewing files that changed from the base of the PR and between 6b01f29 and d3ca156.

📒 Files selected for processing (2)
  • dpgen2/exploration/task/caly_task_group.py
  • tests/exploration/test_make_task_group_from_config.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Correct the empty-set validation so nested atom choices that cannot produce unique species fail before the random selection loop.

Closes deepmodeling#356

Coding-Agent: Codex
Codex-Version: codex-cli 0.149.1
Model: gpt-5.6-sol
Reasoning-Effort: xhigh
@njzjz-bot
njzjz-bot force-pushed the fix/issue-356-calypso-atom-choices branch from 5507103 to d3ca156 Compare August 26, 2026 11:00
@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Aug 26, 2026
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.45%. Comparing base (6b01f29) to head (d3ca156).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #382      +/-   ##
==========================================
+ Coverage   84.43%   84.45%   +0.01%     
==========================================
  Files         104      104              
  Lines        6110     6110              
==========================================
+ Hits         5159     5160       +1     
+ Misses        951      950       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

wanghan-iapcm

This comment was marked as outdated.

@wanghan-iapcm
wanghan-iapcm dismissed their stale review August 27, 2026 04:12

Retracted. This review was produced without running the mandated /code-review fan-out (the loop skill's section 2); the substitute process used instead has since been shown to miss findings and, in one case, to state a verified-sounding falsehood. Re-reviewing properly.

@wanghan-iapcm wanghan-iapcm left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right that (set(s) - overlap) == 0 compares a set to an int and has never fired — that part of #356's diagnosis is correct, and I confirmed the guard has been dead since it was written in 07df321 (#217, 2024-04-30). But the replacement predicate is not the right test, and turning it on is a net regression: it rejects configurations that work today while still hanging on the ones #356 is about.

The predicate is not the condition the loop needs

overlap is the intersection of all sub-lists, so overlap ⊆ L holds for every L. That makes not (set(L) - overlap) equivalent to set(L) == overlap — "some sub-list equals the global intersection". The loop below needs one element per sub-list, all distinct, which exists exactly when Hall's marriage condition holds. The two are unrelated.

Brute-forcing every family over a 3-symbol universe (399 configs, satisfiability decided by exhaustive search):

sub-lists   total  ok-accept  ok-reject  WRONGLY REJECTED  WRONGLY ACCEPTED
    1          7        0          0            7                0
    2         49       18          3           28                0
    3        343      165         51           82               45
  total      399      183         54          117               45

117 of 399 (29%) are satisfiable configs the guard now rejects. 45 (11%) are impossible configs it still lets through, where the loop hangs exactly as #356 describes.

Run against the real make_calypso_task_group_from_config:

[["Li"]]                              d3ca156~1: SUCCESS            head: ValueError
[["Li","Na","K"],["Na","K"],["K"]]    d3ca156~1: ['Li','Na','K']   head: ValueError
[["Li"],["Li"],["Na","K"]]            head: killed at 8s, exit 137 (still spinning)

Note the first row: every single-sub-list config is now rejected, because with one sub-list the intersection is that sub-list. name_of_atoms: [["Li","Na","K"]] — pick one species at random for a 1-species search — is the simplest use of this feature and it now fails at submit.

And the second row is the example printed in the error message itself. Details inline.

Suggested direction

Hall's condition is exact and cheap at CALYPSO species counts:

from itertools import combinations
sets = [set(s) for s in name_of_atoms]
n = len(sets)
if any(
    len(set().union(*(sets[i] for i in sub))) < k
    for k in range(1, n + 1)
    for sub in combinations(range(n), k)
):
    raise ValueError(
        f"cannot pick {n} distinct species from {name_of_atoms}: "
        "some group of sub-lists has fewer candidates than sub-lists"
    )

I checked this against exhaustive search over the same sweep: zero mismatches. Better still would be to replace the while True rejection sampling with a direct matching that constructs the assignment — then the guard is unnecessary and the hang is impossible by construction.

Merge-order warning, not a change request

PR #405 ("Enable remaining Ruff rules", open) rewrites this same line to any((set(s) - overlap) == 0 for s in name_of_atoms) — a pure map→genexp change that keeps the == 0 bug. git merge-tree confirms a direct conflict on this line. If #405 lands after this and the conflict is resolved carelessly, the always-false guard comes back. Worth sequencing them deliberately. (#383 also touches this file but only conflicts on the test file's insertion point — trivial.)

Not a problem, so nobody re-raises it

The bare ValueError is correct here. I traced the call chain — submit_concurrent_learningworkflow_concurrent_learningmake_naive_exploration_schedulermake_calypso_task_group_from_configset_params — and it all runs client-side during dpgen2 submit, before wf.submit(), not inside a dflow OP. FatalError would be wrong at this layer.

@@ -122,7 +122,7 @@ def set_params(
for temp in name_of_atoms[1:]:
overlap = overlap & set(temp)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things go wrong on this line, and they are the same root cause.

It rejects valid configurations. Since overlap ⊆ atom_choices always holds, this fires whenever a sub-list equals the global intersection. That is the shape of any "narrow the choices as you go" config, and of every single-sub-list config. Verified against the shipped API:

[["Li"]]                             before: SUCCESS   after: ValueError
[["Li","Na"]]                        before: SUCCESS   after: ValueError
[["Li","H"],["La","H"],["H"]]        before: ok       after: ValueError   (Li, La, H is valid)

The example in the error message two lines below is itself valid. [[A,B,C],[B,C],[C]] assigns C→B→A. I ran the real equivalent, [["Li","Na","K"],["Na","K"],["K"]]: at d3ca156~1 it returned ['Li','Na','K']; at this head it raises. So the message has documented a legal config as forbidden since #217, and this change is what makes the code enforce that. Whatever predicate you land on, that sentence needs to go or be corrected — it is the only user-facing description of the rule, and it is wrong.

It still hangs on genuinely impossible configs. [["Li"],["Li"],["Na","K"]] has an empty global intersection, so no sub-list equals it, the guard stays silent, and the loop spins forever — I killed it at 8 seconds, exit 137. That is the same failure #356 reports, one sub-list larger. 45 of 399 swept configs behave this way.

Hall's condition is the exact test; see the review body for a drop-in that I verified has zero mismatches against exhaustive search. Whichever way you go, it would be worth putting the offending name_of_atoms and the computed intersection into the message — as written it prints neither, so a user cannot tell which sub-list tripped it.

tgroup = make_calypso_task_group_from_config(self.config)
self.assertTrue(isinstance(tgroup, CalyTaskGroup))

def test_rejects_impossible_random_atom_choices(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[["Li"],["Li"]] happens to be a case where the wrong rule and the right rule agree, so this test cannot tell them apart. I patched three different predicates into the guard and ran only this test:

PR's rule  (sub-list == global intersection)        PASSED
Hall's condition (correct)                          PASSED
"raise iff any two sub-lists are identical" (wrong)  PASSED

All three. So it would not catch a wrong fix, which is the thing worth catching here.

Separately: if the guard ever regresses, this test hangs rather than fails. I reverted the predicate to the pre-PR always-false form and ran it under timeout -k 2 15:

Terminated
EXIT_CODE=124

No pytest verdict at all — assertRaisesRegex is wrapping a call that enters an unbounded while True. A wedged CI job is a worse signal than a red one.

To be fair to it, the test does pin something real: that some guard fires before the retry loop for this input. Three additions would make it discriminating and safe:

  • a positive case that must succeed — [["Li","Na","K"],["Na","K"],["K"]], or just [["Li"]] — which fails today;
  • a true negative with an empty intersection — [["Li"],["Li"],["Na"]] — which currently hangs;
  • and asserting on the predicate directly, or adding a timeout, so a regression reports instead of wedging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Code scan] Fix CALYPSO nested atom-choice validation to avoid infinite loops

2 participants